﻿#region Imports
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using CommonUtilities.Enums;
using CommonUtilities.Extensions;
using CommonUtilities.Messages;
using CommonUtilities.Util;
using DirectoryAnalyzer.Enumerations;
using DirectoryAnalyzer.Events;
using DirectoryAnalyzer.Main;
using DirectoryAnalyzer.Models;
using DirectoryAnalyzer.ResourceFiles;
#endregion

namespace DirectoryAnalyzer.Views
{
    public partial class frmDuplicateFinder : Form
    {
        #region Declarations
        bool isLoading = false;
        bool isAscending = true;
        bool currCheckState = false;
        private string sBasePath = "";
        bool supressCloseConfirmation = false;
        private DateTime startTime;
        string sDeletePath = "Delete ~ path";
        string sOpenPath = "Open ~ path";
        string sClearPath = "Clear path";
        string sExpandedResults = "Expanded results?";
        #endregion

        #region Properties
        public ObservableCollection<DuplicateFiles> AllDuplicates { get; set; }
        #endregion

        #region Constructors
        public frmDuplicateFinder()
        {
            InitializeComponent();

            prgSearchProgress.Parent = pnlProgress;
            lblSearchProgress.Parent = pnlProgress;
            lblSearchProgress.BackColor = Color.Transparent;
            if (dupSearchResult == null) { dupSearchResult = new Controls.DuplicateFileBox(); hostSearchResult.Child = dupSearchResult; }
            dupSearchResult.AccordionStateChangedEventHandler += new EventHandler(dupSearchResult_AccordionStateChanged);
            dupSearchResult.DupItemCheckChangedEventHandler += new System.Windows.RoutedEventHandler(dupSearchResult_DupItemCheckChanged);
            chkExpnadCollapse.ThreeState = false;
            currCheckState = chkExpnadCollapse.Checked = true;

            RefreshButtons(eRefreshType.Init);
        }
        #endregion

        #region Events
        private void frmDuplicateFinder_FormClosing(object sender, FormClosingEventArgs e)
        {
            FormClosingEvent._FormClosing(sender, e, ref supressCloseConfirmation, this);
        }

        private void frmDuplicateFinder_Resize(object sender, EventArgs e)
        {
            FormResizeEvent._FormResize(sender, e, this);
        }

        private void btnDeletePath_Click(object sender, EventArgs e)
        {
            ObservableCollection<string> selectedPaths = lstPath.SelectedItems.Cast<string>().ToObservableCollection();
            ObservableCollection<string> allPaths = lstPath.Items.Cast<string>().ToObservableCollection();
            selectedPaths.ToList().ForEach(path => allPaths.Remove(path));
            lstPath.DataSource = allPaths.ToObservableCollection();

            RefreshButtons(eRefreshType.Delete);
        }

        private void btnSearchPath_Click(object sender, EventArgs e)
        {
            isLoading = true;
            startTime = DateTime.Now;

            ObservableCollection<FDupInfo> allFiles = new ObservableCollection<FDupInfo>();
            AllDuplicates = new ObservableCollection<DuplicateFiles>();
            dupSearchResult.ClearSearchResults();

            ObservableCollection<string> allPaths = new ObservableCollection<string>();
            //foreach (string path in lstPath.Items)
            lstPath.Items.Cast<string>().ToList().ForEach(path =>
                Directory.GetFiles(path, "*.*", SearchOption.AllDirectories).Distinct()
                    .ToList().ForEach(fpath => allPaths.Add(fpath)));
            allPaths.Distinct().ToList()
                .ForEach(fpath => allFiles.Add(new FDupInfo(fpath, chkFastSearch.Checked))); // Remove duplicates resulted from directories and children

            #region Sorting Options
            // RKD
            // http://stackoverflow.com/questions/14293990/how-to-handle-ienumerable-with-igrouping-in-the-view
            // http://stackoverflow.com/questions/8521025/how-to-get-values-from-igrouping

            // Grouping files by field
            foreach (string _groupingfield in allFiles.GroupBy(file => GetSortingField(file)).Select(file => GetSortingField(file.First())))
            {
                ObservableCollection<FDupInfo> duplicates = new ObservableCollection<FDupInfo>();
                allFiles.Where(file => (string)GetSortingField(file) == _groupingfield && file.ByteSize > 0)
                    .ForEach(file => duplicates.Add(new FDupInfo(file, !duplicates.HasCount(0))));

                if (duplicates.GetCount() > 1)
                {
                    if (!chkIncludeThumbs.Checked || (chkIncludeThumbs.Checked && duplicates.Where(dup => dup.IsThumbnail)._None()))
                        AllDuplicates.Add(new DuplicateFiles(chkIncludeThumbs.Checked) { Duplicates = duplicates });
                }
            }

            // Zero-size files
            if (chkIncludeEmptyFiles.Checked) AllDuplicates.Add(new DuplicateFiles(chkIncludeThumbs.Checked) { Duplicates = allFiles.Where(file => file.ByteSize == 0).Cast<FDupInfo>().ToObservableCollection() });

            // Thumbnail files
            if (chkIncludeThumbs.Checked) AllDuplicates.Add(new DuplicateFiles(chkIncludeThumbs.Checked) { Duplicates = allFiles.Where(file => file.IsThumbnail).Cast<FDupInfo>().ToObservableCollection() });

            // Sorting files
            eSortDirection sortDirection = !chkReverseSort.Checked ? eSortDirection.Ascending : eSortDirection.Descending; // Get soring direction
            AllDuplicates = AllDuplicates.OrderBy(dup => GetSortOrder(dup)).ToObservableCollection(); // Empty file always on top
            AllDuplicates.ToList().ForEach(dup => dup.Duplicates = dup.Duplicates.OrderByDirection(_file => GetSortingDate(_file), sortDirection).ToObservableCollection()); // Sort by selected field
            #endregion

            #region Update in UI
            //AllDuplicates.ToList().ForEach(dup => dup.IsResultExpanded = chkExpnadCollapse.Checked); // Expand/Collapse all items based on selected option
            AllDuplicates.ToList()
                .ForEach(dup => dup.Duplicates.ToList().ForEach(file => file.IsSelectedForDeletion = (file != dup.Duplicates.First()))); // Mark all files for deletion except first
            AllDuplicates.Where(dup => dup.IsFileEmpty || dup.IsThumbnail).ToList()
                .ForEach(dup => dup.Duplicates.ToList().ForEach(file => file.IsSelectedForDeletion = true)); // Empty file always marked for deletion
            dupSearchResult.AddSearchResults(AllDuplicates, chkShowCustom.Checked);
            MessageBoxUtil.Show(Program.IsDebug, ResourceDirectoryAnalyzer.Status_AnalysisCompleted.FormatTextFromParams(Utilities.GetDuration(startTime)), ResourceDirectoryAnalyzer.App_Completed, MessageBoxButtons.OK, MessageBoxIcon.Information);
            chkExpnadCollapse.CheckState = CheckState.Checked;
            chkExpnadCollapse.Enabled = true;
            UpdateSelectionCheckboxText();
            isLoading = false;
            #endregion

            RefreshButtons(eRefreshType.Search);
        }
        #endregion

        /// <summary>
        /// Sort empty files first, then thumbnails, and then remaining files at end
        /// </summary>
        /// <param name="dup">Duplicate files</param>
        /// <returns>Sort index</returns>
        private int GetSortOrder(DuplicateFiles dup)
        {
            return dup.IsFileEmpty ? 0 : (dup.IsThumbnail ? 1 : 2);
        }

        private DateTime GetSortingDate(FInfo _file)
        {
            if (radSortByCreated.Checked)
                return _file.CreatedDate;
            else if (radSortByModified.Checked)
                return _file.LastModified;
            else if (radSortByLastAccessed.Checked)
                return _file.LastAccessed;

            return DateTime.MinValue;
        }

        private object GetSortingField(FInfo _file)
        {
            return chkFastSearch.Checked ? _file.ByteSize.ToString() : _file.HashCode;
        }

        private void chkShowCustom_CheckedChanged(object sender, EventArgs e)
        {
            isLoading = true;
            dupSearchResult.IsEnabled = !isLoading;
            if (chkShowCustom.Checked && dupSearchResult.accResult.Visibility == System.Windows.Visibility.Visible)
                AllDuplicates.ToList().ForEach(dup => dup.IsResultExpanded = dupSearchResult.accResult.SelectedItems.Cast<DuplicateFiles>()._Contains(dup));
            else
                AllDuplicates.Where(dup => dup.IsResultExpanded = true).ToList()
                    .ForEach(dup => dupSearchResult.accResult.SelectedItems.Add(dup));
            dupSearchResult.UpdateControlView(chkShowCustom.Checked);
            isLoading = false;
            dupSearchResult.IsEnabled = !isLoading;
        }

        private void chkExpnadCollapse_CheckedChanged(object sender, EventArgs e)
        {
            if (!isLoading && AllDuplicates.Any())
            {
                if (chkExpnadCollapse.CheckState != CheckState.Indeterminate)
                {
                    dupSearchResult.IsEnabled = false;
                    //chkExpnadCollapse.Checked = !currCheckState; RKD
                    AllDuplicates.ToList().ForEach(dup => dup.IsResultExpanded = chkExpnadCollapse.Checked);
                    dupSearchResult.AddSearchResults(AllDuplicates, chkShowCustom.Checked);
                    dupSearchResult.IsEnabled = true;
                }
            }

            UpdateSelectionCheckboxText();
        }

        private void UpdateSelectionCheckboxText()
        {
            switch (chkExpnadCollapse.CheckState)
            {
                case CheckState.Checked:
                    chkExpnadCollapse.Text = sExpandedResults + " Mode :: All";
                    break;
                case CheckState.Indeterminate:
                    chkExpnadCollapse.Text = sExpandedResults + " Mode :: Some";
                    break;
                case CheckState.Unchecked:
                default:
                    chkExpnadCollapse.Text = sExpandedResults + " Mode :: None";
                    break;
            }
        }

        /// <summary>
        /// Occurs when an accordion item is expanded/collapsed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void dupSearchResult_AccordionStateChanged(object sender, EventArgs e)
        {
            if (!isLoading && AllDuplicates.Any())
            {
                if (AllDuplicates.Select(dup => dup.IsResultExpanded).Distinct().Count() > 1)
                {
                    isLoading = true;
                    currCheckState = chkExpnadCollapse.Checked;
                    chkExpnadCollapse.ThreeState = true;
                    chkExpnadCollapse.CheckState = CheckState.Indeterminate;
                    UpdateSelectionCheckboxText();
                    isLoading = false;
                }
                else
                {
                    isLoading = true;
                    chkExpnadCollapse.ThreeState = false;
                    currCheckState = AllDuplicates.Select(dup => dup.IsResultExpanded).Distinct().First();
                    chkExpnadCollapse.CheckState = (currCheckState ? CheckState.Checked : CheckState.Unchecked);
                    UpdateSelectionCheckboxText();
                    isLoading = false;
                }
            }
        }

        private void dupSearchResult_DupItemCheckChanged(object sender, System.Windows.RoutedEventArgs e)
        {

        }

        private void btnClearResults_Click(object sender, EventArgs e)
        {
            AllDuplicates = new ObservableCollection<DuplicateFiles>();
            dupSearchResult.ClearSearchResults();
            chkExpnadCollapse.Enabled = false;
            Notifications.PlaySound(eNotificationSound.Cleanup, Program.IsMute);
        }

        private void btnDeleteAll_Click(object sender, EventArgs e)
        {
            // REQ ##11
            AllDuplicates.ToList()
                .ForEach(dup => dup.Duplicates.ToList().ForEach(file => file.IsSelectedForDeletion = (file != dup.Duplicates.First()))); // Mark all files for deletion except first
            AllDuplicates.Where(dup => dup.ByteSize == 0).ToList()
                .ForEach(dup => dup.Duplicates.ToList().ForEach(file => file.IsSelectedForDeletion = true)); // Empty file always marked for deletion

            DeleteFiles(AllDuplicates);
        }

        private void DeleteFiles(ObservableCollection<DuplicateFiles> allDuplicates)
        {
            ObservableCollection<FDupInfo> allDelete = new ObservableCollection<FDupInfo>();
            allDuplicates.ToList().ForEach(dup => dup.Duplicates.ToList()
                .ForEach(file => allDelete.Add(file)));

            // RKD : Deletion logic
            allDelete.ToList().ForEach(file => DeleteFile(file));
            Process.Start(@"H:\Dropbox\VS2010\DirectoryAnalyzer\test_data.bat"); // RKD : temp
            // TEST logic
            //string checkedDups = "";
            //allDelete.ToList().ForEach(file => checkedDups += file.IsSelectedForDeletion ? file.RelPath + "\n" : "");
            //MessageBoxUtil.Show(Program.IsDebug, "Selected for deletion:\n" + checkedDups);

            // Update in UI
            dupSearchResult.AddSearchResults(allDuplicates, true);
            //switch (MessageBoxUtil.Show(Program.IsDebug, "Reload files in UI?\t\t", "Confirmation..", MessageBoxButtons.YesNo, MessageBoxIcon.Question)) RKD
            //{
            //    case DialogResult.Yes:
            //        dupSearchResult.AddSearchResults(AllDuplicates, true);
            //        break;
            //    case DialogResult.No:
            //    default:
            //        break;
            //}
        }

        private void DeleteFile(FDupInfo file)
        {
            if (file.IsSelectedForDeletion) File.Delete(file.FilePath);
        }

        private void btnDeleteChecked_Click(object sender, EventArgs e)
        {
            // REQ ##12
            DeleteFiles(AllDuplicates);
        }

        private void lstPath_SelectedIndexChanged(object sender, EventArgs e)
        {
            RefreshButtons(eRefreshType.Selection);
        }

        private void btnClearPath_Click(object sender, EventArgs e)
        {
            lstPath.DataSource = new ObservableCollection<string>(); // Clear all paths

            isAscending = true;
            RefreshButtons(eRefreshType.Clear);
            Notifications.PlaySound(eNotificationSound.Cleanup, Program.IsMute); // Play trash sound
        }

        private void btnSortPath_Click(object sender, EventArgs e)
        {
            ObservableCollection<string> allPaths = new ObservableCollection<string>();
            lstPath.DataSource = lstPath.Items.Cast<string>()
                .OrderByDirection(dup => dup, isAscending ? eSortDirection.Ascending : eSortDirection.Descending).ToObservableCollection(); // Sorting here
            isAscending = !isAscending;
        }

        private void cmnuOpenPath_Click(object sender, EventArgs e)
        {
            ObservableCollection<string> selectedPaths = lstPath.SelectedItems.Cast<string>().ToObservableCollection();
            selectedPaths.ToList().ForEach(path => Process.Start(path));
        }

        private void btnAddPath_Click(object sender, EventArgs e)
        {
            frmPathSelector frmPathSelector = new frmPathSelector();
            frmPathSelector.ShowDialog(this);

            switch (frmPathSelector.DialogResult)
            {
                case DialogResult.OK:
                    ObservableCollection<string> allPaths = new ObservableCollection<string>();
                    allPaths = lstPath.Items.Cast<string>().ToObservableCollection();
                    allPaths.Add(frmPathSelector.txtPath.Text);
                    lstPath.DataSource = allPaths.ToObservableCollection();
                    break;

                case DialogResult.Cancel:
                default:
                    break;
            }

            frmPathSelector = null;

            /* RKD
            if (!Directory.Exists(txtPath.Text)) MessageBoxUtil.Show(Program.IsDebug, "Invalid path..");
            else
            {
                if (lstPath.Items.Cast<string>().ToList().BinarySearch(txtPath.Text) < 0) // Search if already added
                {
                    ObservableCollection<string> allPaths = new ObservableCollection<string>();
                    allPaths = lstPath.Items.Cast<string>().ToObservableCollection();
                    allPaths.Add(txtPath.Text);
                    lstPath.DataSource = allPaths.ToObservableCollection();

                    btnSearchPath.Enabled = cmnuClearPath.Enabled = btnClearPath.Enabled = true;
                    cmnuSortPath.Enabled = btnSortPath.Enabled = (lstPath.Items.Count > 1);
                    cmnuClearPath.Text = btnClearPath.Text = sClearPath + (lstPath.Items.Count > 1 ? "s" : ""); // Pluralize button text 
                }
                else MessageBoxUtil.Show(Program.IsDebug, "Path already added..\t", "Validation..", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            */
        }

        internal void RefreshButtons(eRefreshType refreshType)
        {
            switch (refreshType)
            {
                case eRefreshType.Init:
                case eRefreshType.Clear:
                    cmnuDeletePath.Text = btnDeletePath.Text = sDeletePath.RemoveText("~ "); // Update text as 'Delete path"
                    cmnuClearPath.Text = btnClearPath.Text = sClearPath; // Update text as 'Clear path"
                    chkExpnadCollapse.Enabled = chkExpnadCollapse.Enabled = btnSearchPath.Enabled = cmnuSortPath.Enabled = btnSortPath.Enabled = cmnuDeletePath.Enabled = btnDeletePath.Enabled = cmnuClearPath.Enabled = btnClearPath.Enabled = false; // Disable 'Delete/Clear path' and 'Start search' buttons
                    break;

                case eRefreshType.Search:
                    chkExpnadCollapse.Enabled = AllDuplicates.Any();
                    break;

                case eRefreshType.Add:
                    btnSearchPath.Enabled = cmnuClearPath.Enabled = btnClearPath.Enabled = true;
                    cmnuSortPath.Enabled = btnSortPath.Enabled = (lstPath.Items.Count > 1);
                    cmnuClearPath.Text = btnClearPath.Text = sClearPath + (lstPath.Items.Count > 1 ? "s" : ""); // Pluralize button text 
                    break;

                case eRefreshType.Delete:
                    chkExpnadCollapse.Enabled = btnSearchPath.Enabled = cmnuDeletePath.Enabled = btnDeletePath.Enabled = lstPath.Items.Cast<string>().Any();
                    cmnuDeletePath.Text = btnDeletePath.Text = sDeletePath.RemoveText("~ ");
                    cmnuSortPath.Enabled = btnSortPath.Enabled = (lstPath.Items.Count > 1);
                    break;

                case eRefreshType.Selection:
                    int selCount = lstPath.SelectedItems.Count; // Selection count
                    cmnuOpenPath.Enabled = cmnuDeletePath.Enabled = btnDeletePath.Enabled = (selCount > 0); // Enable 'Delete path' only if any files are selected
                    cmnuDeletePath.Text = btnDeletePath.Text = sDeletePath.RemoveText("~", selCount.ToString()) + (selCount > 1 ? "s" : ""); // Pluralize button text with selection count
                    cmnuOpenPath.Text = sOpenPath.RemoveText("~", selCount.ToString()) + (selCount > 1 ? "s" : ""); // Pluralize button text with selection count
                    break;

                default:
                    break;
            }
        }
    }
}

//Search for all 'RKD' and add to logger